/**
 * 
 */
package net.phoenix.repository.query.jql;

import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;

import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.Workspace;
import javax.jcr.query.InvalidQueryException;
import javax.jcr.query.Query;
import javax.jcr.query.QueryResult;
import javax.jcr.query.qom.Column;
import javax.jcr.query.qom.Constraint;
import javax.jcr.query.qom.Ordering;
import javax.jcr.query.qom.QueryObjectModel;
import javax.jcr.query.qom.Source;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.phoenix.repository.query.GeneralQuery;
import net.phoenix.repository.query.sql.SQLQuery;
import antlr.ASTFactory;

/**
 * @author lixf
 * 
 */
public class JQLQuery extends GeneralQuery implements QueryObjectModel {
	private QueryAST root;
	private static ASTFactory factory;
	private JQL2SQL converter;
	private Map<String, Value> parameters;
	private static Log log = LogFactory.getLog(JQLQuery.class);

	public JQLQuery(){}
	@Override
	public void init(Workspace workspace, String statement) throws InvalidQueryException {
		super.init(workspace, statement);
		if (factory == null)
			factory = new JQLASTFactory();

		// parser the statement;
		StringReader cs = new StringReader(statement);
		JQLLexer lexer = new JQLLexer(cs);
		JQLParser parser = new JQLParser(lexer);
		parser.setASTFactory(factory);
		try {
			parser.statement();
		} catch (Exception e) {
			throw new InvalidQueryException(e);
		}
		this.root = (QueryAST) parser.getAST();

		try {
			this.converter = new JQL2SQL(workspace.getNodeTypeManager());
		} catch (RepositoryException e) {
			throw new InvalidQueryException(e);
		}
		// collect information from result;
		this.root.generateSQL(converter);

		this.parameters = new HashMap<String, Value>();

	}

	public ValueFactory getValueFactory() throws RepositoryException {
		return this.workspace.getSession().getValueFactory();
	}

	public String getSQL() {
		return this.converter.getSQL();
	}

	@Override
	public void bindValue(String arg0, Value arg1) throws IllegalArgumentException, RepositoryException {
		this.parameters.put(arg0, arg1);
	}

	@Override
	public QueryResult execute() throws InvalidQueryException, RepositoryException {
		this.checkBindVariables();
		String sql = this.converter.getSQL();
		log.debug("convert from: "+ this.getStatement());
		log.debug("to:"+ sql);
		SQLQuery query = new SQLQuery();
		query.init(this.workspace, sql);
		String[] names = this.getBindVariableNames();
		for (int i = 0; i < names.length; i++)
			query.bindValue("" + (i+1), this.parameters.get(names[i]));
		query.setLimit(this.limit);
		query.setOffset(this.offset);
		return query.execute();
	}

	private void checkBindVariables() throws InvalidQueryException, RepositoryException {
		for (String name : this.getBindVariableNames()) {
			if (!this.parameters.keySet().contains(name))
				throw new InvalidQueryException("key '" + name + "' has not bind in query " + this.statement + ".");
		}
	}

	@Override
	public String[] getBindVariableNames() throws RepositoryException {
		return this.converter.getParameters();
	}

	@Override
	public String getLanguage() {
		return Query.JCR_JQOM;
	}

	@Override
	public Source getSource() {
		FromClauseAST from = this.root.getFromClauseAST();
		if (from != null)
			return from.getSource();
		return null;
	}

	@Override
	public Constraint getConstraint() {
		WhereAST where = this.root.getWhereAST();
		if (where != null)
			return (Constraint) where.getFirstChild();
		return null;
	}

	@Override
	public Ordering[] getOrderings() {
		OrderByAST ast = this.root.getOrderByAST();
		if (ast != null)
			return this.root.getOrderByAST().getOrderings();
		return null;
	}

	@Override
	public Column[] getColumns() {
		return this.root.getSelectClauseAST().getColumns();
	}

}
